﻿<#
	File      = Update-WmiFunctionLibrary.ps1
	Author    = Kirk "Poshoholic" Munro
	Publisher = Poshoholic Studios
	Copyright = © 2012 Poshoholic Studios. All rights reserved.
#>

#region Update PSScriptRoot if it is not set for files yet.

if (-not $PSScriptRoot) {
    $PSScriptRoot = $MyInvocation.MyCommand.Path | Split-Path -Parent
}

#endregion

function Update-WmiFunctionLibrary {
	[CmdletBinding()]
	param(
		[Parameter()]
		[ValidateNotNullOrEmpty()]
		[System.String[]]
		$Namespace = @('ROOT\cimv2'),

		[Parameter(Position=0)]
		[ValidateNotNullOrEmpty()]
		[System.String]
		$Class,

		[Parameter()]
		[ValidateNotNull()]
		[System.Management.ImpersonationLevel]
		$Impersonation,

		[Parameter()]
		[ValidateNotNull()]
		[System.Management.AuthenticationLevel]
		$Authentication,

		[Parameter()]
		[ValidateNotNullOrEmpty()]
		[System.String]
		$Locale,

		[Parameter()]
		[System.Management.Automation.SwitchParameter]
		$EnableAllPrivileges,

		[Parameter()]
		[ValidateNotNullOrEmpty()]
		[System.String]
		$Authority,

		[Parameter()]
		[ValidateNotNull()]
		[System.Management.Automation.Credential()]
		$Credential = [System.Management.Automation.PSCredential]::Empty,

		[Parameter()]
		[ValidateNotNull()]
		[System.Int32]
		$ThrottleLimit,

		[Parameter()]
		[ValidateNotNullOrEmpty()]
		[Alias('Cn')]
		[System.String[]]
		$ComputerName,

		[Parameter()]
		[System.Management.Automation.SwitchParameter]
		$Recurse
	)

	try {
		#region Ensure that the caller is using an elevated session if they are processing WMI on the local system.

		if ((-not $PSCmdlet.MyInvocation.BoundParameters.ContainsKey('ComputerName')) -and
			([System.Environment]::OSVersion.Version.Major -gt 5)) {
			$currentIdentity = [System.Security.Principal.WindowsIdentity]::GetCurrent()
			$windowsPrincipal = Microsoft.PowerShell.Utility\New-Object -TypeName System.Security.Principal.WindowsPrincipal($currentIdentity)
			if (-not $windowsPrincipal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)) {
				[System.String]$message = $StringTable.ErrorElevationRequired
				[System.UnauthorizedAccessException]$exception = Microsoft.PowerShell.Utility\New-Object -TypeName System.UnauthorizedAccessException -ArgumentList $message
				[System.Management.Automation.ErrorRecord]$errorRecord = Microsoft.PowerShell.Utility\New-Object -TypeName System.Management.Automation.ErrorRecord -ArgumentList $exception,'PermissionDenied',([System.Management.Automation.ErrorCategory]::PermissionDenied),'Update-WmiFunctionLibrary'
				$PSCmdlet.ThrowTerminatingError($errorRecord)
			}
		}

		#endregion

		#region If wmifx is loaded, unload it before updating the module.

		if (Get-Module -Name wmifx) {
			Remove-Module -Name wmifx
		}

		#endregion

		Use-PspxModule {
			Write-Progress -Activity $StringTable.ProgressOnUpdateWmifxTitle -Status $StringTable.ProgressOnUpdateWmifxMessageInitializing

			#region Define the WMI class validation script block that is used in all non-SCCM proxy functions.

			$classValidationScriptBlock = {
				#region Verify that the WMI class exists on the machines where we are attempting to retrieve it.

				$getWmiObjectPassThruParameters = @{
					Namespace = $Namespace
					    Class = $Class
					     List = $true
				}
				foreach ($optionalParameterName in @('Impersonation','Authentication','EnableAllPrivileges','Authority','Credential')) {
					if ($PSPassThruParameters.ContainsKey($optionalParameterName)) {
						$getWmiObjectPassThruParameters[$optionalParameterName] = $PSPassThruParameters[$optionalParameterName]
					}
				}
				if ($PSPassThruParameters.ContainsKey('ComputerName')) {
					$invalidComputerNames = @()
					foreach ($item in $PSPassThruParameters.ComputerName) {
						if (-not (Get-WmiObject @getWmiObjectPassThruParameters -ComputerName $item)) {
							$invalidComputerNames += $item
						}
					}
					if ($invalidComputerNames.Count -eq $PSPassThruParameters.ComputerName.Count) {
						[System.String]$message = $StringTable.ErrorWmiClassNotAvailableOnRemoteSystems -f $Class,($invalidComputerNames -join "$($StringTable.ListSeparator) ")
						[System.InvalidOperationException]$exception = Microsoft.PowerShell.Utility\New-Object -TypeName System.InvalidOperationException -ArgumentList $message
						[System.Management.Automation.ErrorRecord]$errorRecord = Microsoft.PowerShell.Utility\New-Object -TypeName System.Management.Automation.ErrorRecord -ArgumentList $exception,'InvalidOperation',([System.Management.Automation.ErrorCategory]::InvalidOperation),'Get-WmiObject'
						$PSCmdlet.ThrowTerminatingError($errorRecord)
					} else {
						$PSPassThruParameters.ComputerName = $PSPassThruParameters.ComputerName | Where-Object {$invalidComputerNames -notcontains $_}
						foreach ($item in $invalidComputerNames) {
							[System.String]$message = $StringTable.ErrorWmiClassNotAvailableOnRemoteSystems -f $Class,$item
							[System.InvalidOperationException]$exception = Microsoft.PowerShell.Utility\New-Object -TypeName System.InvalidOperationException -ArgumentList $message
							[System.Management.Automation.ErrorRecord]$errorRecord = Microsoft.PowerShell.Utility\New-Object -TypeName System.Management.Automation.ErrorRecord -ArgumentList $exception,'InvalidOperation',([System.Management.Automation.ErrorCategory]::InvalidOperation),'Get-WmiObject'
							$PSCmdlet.WriteError($errorRecord)
						}
					}
				} elseif (-not (Get-WmiObject @getWmiObjectPassThruParameters)) {
					[System.String]$message = $StringTable.ErrorWmiClassNotAvailableOnLocalSystem -f $Class
					[System.InvalidOperationException]$exception = Microsoft.PowerShell.Utility\New-Object -TypeName System.InvalidOperationException -ArgumentList $message
					[System.Management.Automation.ErrorRecord]$errorRecord = Microsoft.PowerShell.Utility\New-Object -TypeName System.Management.Automation.ErrorRecord -ArgumentList $exception,'InvalidOperation',([System.Management.Automation.ErrorCategory]::InvalidOperation),'Get-WmiObject'
					$PSCmdlet.ThrowTerminatingError($errorRecord)
				}

				#endregion
			}

			#endregion

			#region Define the SCCM validation script block that is used in all SCCM proxy functions.

			$sccmValidationScriptBlock = {
				#region Define the sccmSiteCodes script variable if it does not exist yet.

				if (-not (Get-Variable -Scope Script -Name SccmSiteCodes -ErrorAction SilentlyContinue)) {
					New-Variable -Scope Script -Name SccmSiteCodes -Description 'Site codes for any SCCM servers that have been touched by this module.' -Value @{}
				}

				#endregion

				#region Lookup the local site codes for the SCCM servers we are connecting to if we don't know them already.

				$getWmiObjectPassThruParameters = @{
					Namespace = 'root\sms'
					    Class = 'SMS_ProviderLocation'
					   Filter = 'ProviderForLocalSite=True'
				}
				foreach ($optionalParameterName in @('Impersonation','Authentication','EnableAllPrivileges','Authority','Credential')) {
					if ($PSPassThruParameters.ContainsKey($optionalParameterName)) {
						$getWmiObjectPassThruParameters[$optionalParameterName] = $PSPassThruParameters[$optionalParameterName]
					}
				}
				if ($PSPassThruParameters.ContainsKey('ComputerName')) {
					$invalidComputerNames = @()
					foreach ($item in $PSPassThruParameters.ComputerName) {
						if ((-not $script:SccmSiteCodes.ContainsKey($item)) -and
						    ($smsProviderLocation = Get-WmiObject @getWmiObjectPassThruParameters -ComputerName $item -ErrorAction SilentlyContinue)) {
							$script:SccmSiteCodes[$item] = $smsProviderLocation.SiteCode
						}
					}
				} elseif (-not $script:SccmSiteCodes.ContainsKey($env:COMPUTERNAME)) {
					if ($smsProviderLocation = Get-WmiObject @getWmiObjectPassThruParameters -ErrorAction SilentlyContinue) {
						$script:SccmSiteCodes[$env:COMPUTERNAME] = $smsProviderLocation.SiteCode
					}
				}

				#endregion

				#region Now that we have the site codes, verify that each of the machines we are using are SCCM servers.

				if ($PSPassThruParameters.ContainsKey('ComputerName')) {
					$invalidComputerNames = @()
					foreach ($item in $PSPassThruParameters.ComputerName) {
						if (-not $script:SccmSiteCodes.ContainsKey($item)) {
							$invalidComputerNames += $item
						}
					}
					if ($invalidComputerNames.Count -eq $PSPassThruParameters.ComputerName.Count) {
						[System.String]$message = $StringTable.ErrorSccmNotAvailableOnRemoteSystems -f ($invalidComputerNames -join "$($StringTable.ListSeparator) ")
						[System.InvalidOperationException]$exception = Microsoft.PowerShell.Utility\New-Object -TypeName System.InvalidOperationException -ArgumentList $message
						[System.Management.Automation.ErrorRecord]$errorRecord = Microsoft.PowerShell.Utility\New-Object -TypeName System.Management.Automation.ErrorRecord -ArgumentList $exception,'InvalidOperation',([System.Management.Automation.ErrorCategory]::InvalidOperation),'Get-WmiObject'
						$PSCmdlet.ThrowTerminatingError($errorRecord)
					} else {
						$PSPassThruParameters.ComputerName = $PSPassThruParameters.ComputerName | Where-Object {$invalidComputerNames -notcontains $_}
						foreach ($item in $invalidComputerNames) {
							[System.String]$message = $StringTable.ErrorSccmNotAvailableOnRemoteSystem -f $item
							[System.InvalidOperationException]$exception = Microsoft.PowerShell.Utility\New-Object -TypeName System.InvalidOperationException -ArgumentList $message
							[System.Management.Automation.ErrorRecord]$errorRecord = Microsoft.PowerShell.Utility\New-Object -TypeName System.Management.Automation.ErrorRecord -ArgumentList $exception,'InvalidOperation',([System.Management.Automation.ErrorCategory]::InvalidOperation),'Get-WmiObject'
							$PSCmdlet.WriteError($errorRecord)
						}
					}
				} elseif (-not $script:SccmSiteCodes.ContainsKey($env:COMPUTERNAME)) {
					[System.String]$message = $StringTable.ErrorSccmNotAvailableOnLocalSystem
					[System.InvalidOperationException]$exception = Microsoft.PowerShell.Utility\New-Object -TypeName System.InvalidOperationException -ArgumentList $message
					[System.Management.Automation.ErrorRecord]$errorRecord = Microsoft.PowerShell.Utility\New-Object -TypeName System.Management.Automation.ErrorRecord -ArgumentList $exception,'InvalidOperation',([System.Management.Automation.ErrorCategory]::InvalidOperation),'Get-WmiObject'
					$PSCmdlet.ThrowTerminatingError($errorRecord)
				}

				#endregion
			}

			#endregion

			#region Create the wmifx module root folder if the module does not exist already.

			if ($PSVersionTable.PSVersion -eq [System.Version]'2.0') {
				<#
				PowerShell 2.0 Crash:
				The following Get-Module call must not be invoked directly from this function in PowerShell
				2.0. If it is invoked directly, then PowerShell will crash with a StackOverflowException
				when this module is unloaded via Remove-Module. This bug appears to be fixed in version 3.0.
				#>
				$job = Start-Job -ScriptBlock {Get-Module -ListAvailable -Name wmifx | Select-Object -ExpandProperty ModuleBase -First 1}
				$wmifxRoot = Wait-Job -Job $job | Receive-Job
				Remove-Job -Job $job
			} else {
				$wmifxRoot = Get-Module -ListAvailable -Name wmifx | Select-Object -ExpandProperty ModuleBase -First 1
			}
			if (-not $wmifxRoot) {
				try {
					$wmifxRoot = Get-Item -LiteralPath $PSScriptRoot | Split-Path -Parent | Join-Path -ChildPath wmifx | New-Item -Path {$_} -ItemType Directory -Force | Select-Object -ExpandProperty FullName
				} catch {
					$wmifxRoot = New-Item -Path "$([System.Environment]::GetFolderPath('MyDocuments'))\WindowsPowerShell\Modules\wmifx" -ItemType Directory -Force | Select-Object -ExpandProperty FullName
					if ($?) {
						$wmifxRoot = "$([System.Environment]::GetFolderPath('MyDocuments'))\WindowsPowerShell\Modules\wmifx"
					}
				}
			}

			#endregion

			Write-Progress -Activity $StringTable.ProgressOnUpdateWmifxTitle -Status $StringTable.ProgressOnUpdateWmifxMessageAnalyzingExistingModule

			#region Identify the WMI classes that already have type data extensions.

			<#
			PowerShell 3.0 Breaking Change:
			$Host.Runspace.RunspaceConfiguration is not used to store the type files that PowerShell
			loads. These are now stored in $Host.Runspace.InitialSessionState.Types in PowerShell 3.0
			and later. To work around this, script authors have to check the version and make the
			appropriate call.
			#>
			$typeFiles = @()
			if ($PSVersionTable.PSVersion -lt [System.Version]'3.0') {
				if ($Host.Runspace.RunspaceConfiguration -ne $null) {
					$typeFiles = @($Host.Runspace.RunspaceConfiguration.Types | Where-Object {-not $_.FileName.StartsWith($wmifxRoot)} | Select-Object -ExpandProperty FileName)
				}
			} else {
				if ($Host.Runspace.InitialSessionState -ne $null) {
					$typeFiles = @($Host.Runspace.InitialSessionState.Types | Where-Object {-not $_.FileName.StartsWith($wmifxRoot)} | Select-Object -ExpandProperty FileName)
				}
			}
			$typeFiles += @(Get-ChildItem -LiteralPath $wmifxRoot -Recurse -Filter types*.ps1xml | Select-Object -ExpandProperty FullName)
			$wmiTypeDataExtensionsInternal = @()
			$wmiTypeDataExtensionsExternal = @{}
			foreach ($typeFile in $typeFiles) {
				if ($typeFile.StartsWith($wmifxRoot)) {
					$wmiTypeDataExtensionsInternal += @(Select-Xml -Path $typeFile -XPath 'Types/Type/Name' | Select-Object -ExpandProperty Node | Select-Object -ExpandProperty '#text') -match '^System\.Management\.ManagementObject#'
				} else {
					Select-Xml -Path $typeFile -XPath 'Types/Type/Name' `
						| Where-Object {$_.Node.'#text' -match '^System\.Management\.ManagementObject#'} `
						| ForEach-Object {
							$upper = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
							$lower = 'abcdefghijklmnopqrstuvwxyz'
							$wmiTypeDataExtensionsExternal[$_.Node.'#text'] = @($_.Node.SelectNodes("../Members"))[0].InnerXml
							# TODO: Make this InnerXml well formatted for our ps1xml document
						}
				}
			}

			#endregion

			#region Identify the nouns that are already in use in wmifx.

			$nounList = @()
			if (Test-Path -Path $wmifxRoot) {
				foreach ($getScriptFile in Get-ChildItem -Path $wmifxRoot -Filter 'Get-*.ps1' -Recurse | Where-Object {$_.Extension -eq '.ps1'}) {
					$nounList += $getScriptFile.Name -replace '^Get-|\.ps1'
				}
			}

			#endregion

			#region Identify the WMI property adapters that already exist in wmifx.

			$wmiPropertyAdapters = @()
			if (Test-Path -Path $wmifxRoot) {
				foreach ($propertyAdapterDll in Get-ChildItem -Path $wmifxRoot -Filter '*PropertyAdapter*.dll' -Recurse | Where-Object {$_.Extension -eq '.dll'}) {
					$assembly = [System.Reflection.Assembly]::ReflectionOnlyLoadFrom($propertyAdapterDll.FullName)
					$wmiPropertyAdapters += $assembly.GetExportedTypes() | Select-Object -ExpandProperty FullName
				}
			}

			#endregion

			Write-Progress -Activity $StringTable.ProgressOnUpdateWmifxTitle -Status $StringTable.ProgressOnUpdateWmifxMessageProcessingNamespaces

			#region Build a map of namespaces to security settings by expanding the namespace parameter.

			$wmiNamespaceMap = @{}
			foreach ($namespaceIdentifier in $Namespace) {
				$getWmiObjectParameters = @{
					Namespace = $namespaceIdentifier
					    Class = '__NAMESPACE'
					     List = $true
				}
				foreach ($optionalParameterName in @('Impersonation','Authentication','Locale','EnableAllPrivileges','Authority','Credential','ThrottleLimit','ComputerName','Recurse')) {
					if ($PSCmdlet.MyInvocation.BoundParameters.ContainsKey($optionalParameterName)) {
						$getWmiObjectParameters[$optionalParameterName] = $PSCmdlet.MyInvocation.BoundParameters[$optionalParameterName]
					}
				}
				$retry = $false
				try {
					$wmiNamespaces = Get-WmiObject @getWmiObjectParameters | Select-Object -ExpandProperty __NAMESPACE
				} catch {
					if (($_.Exception.ErrorCode -eq [System.Management.ManagementStatus]::AccessDenied) -and
					    ((-not $getWmiObjectParameters.ContainsKey('Authentication')) -or
					     ($getWmiObjectParameters['Authentication'] -ne [System.Management.AuthenticationLevel]::PacketPrivacy))) {
						$retry = $true
					} else {
						throw
					}
				}
				if ($retry) {
					$getWmiObjectParameters['Authentication'] = [System.Management.AuthenticationLevel]::PacketPrivacy
					$wmiNamespaces = Get-WmiObject @getWmiObjectParameters | Select-Object -ExpandProperty __NAMESPACE
				}
				foreach ($item in $wmiNamespaces) {
					if (@('ROOT\cimv2\terminalservices','ROOT\mscluster','ROOT\servicemodel') -contains $item) {
						$wmiNamespaceMap[$item] = [System.Management.AuthenticationLevel]::PacketPrivacy
					} elseif ($getWmiObjectParameters.ContainsKey('Authentication')) {
						$wmiNamespaceMap[$item] = $getWmiObjectParameters['Authentication']
					} else {
						$wmiNamespaceMap[$item] = [System.Management.AuthenticationLevel]::Default
					}
				}
			}
			$Namespace = $wmiNamespaceMap.Keys | Sort-Object

			#endregion

			foreach ($namespaceIdentifier in $Namespace) {
				#region Initialize helper variables.

				$typeData = @{}
				$typeDataHeader = @'
<?xml version="1.0" encoding="utf-8" ?>
<!-- **************************************************************************
This file contains formatting information used by the Windows PowerShell
engine. Do not edit or change the contents of this file directly. It is
automatically generated by the wmix module. Please see the Windows PowerShell
documentation or type Get-Help Update-TypeData for more information about type
data files.

Copyright © 2012 Poshoholic Studios. All rights reserved.
*************************************************************************** -->

<Types>
'@
				$typeDataFooter = '</Types>'
				$defaultAuthenticationLevel = $wmiNamespaceMap[$namespaceIdentifier]

				#endregion

				#region Retrieve WMI class list.

				Write-Progress -Activity $StringTable.ProgressOnUpdateWmifxTitle -Status ($StringTable.ProgressOnUpdateWmifxMessageRetrievingWmiClassList -f $namespaceIdentifier)

				$getWmiObjectParameters = @{
					     Namespace = $namespaceIdentifier
					          List = $true
					       Amended = $true
					Authentication = $defaultAuthenticationLevel
				}
				foreach ($optionalParameterName in @('Class','Impersonation','Locale','EnableAllPrivileges','Authority','Credential','ThrottleLimit','ComputerName')) {
					if ($PSCmdlet.MyInvocation.BoundParameters.ContainsKey($optionalParameterName)) {
						$getWmiObjectParameters[$optionalParameterName] = $PSCmdlet.MyInvocation.BoundParameters[$optionalParameterName]
					}
				}
				$wmiClasses = Get-WmiObject @getWmiObjectParameters

				#endregion

				#region Identify WMI relationships, constructors and destructors.

				Write-Progress -Activity $StringTable.ProgressOnUpdateWmifxTitle -Status ($StringTable.ProgressOnUpdateWmifxMessageIdentifyingOpportunities -f $namespaceIdentifier)

				$wmiRelationshipMap = @{}
				$wmiConstructorMap = @{}
				$wmiDestructorMap = @{}

				foreach ($wmiClass in $wmiClasses) {
					#region Store the list of qualifier names for the class.

					$classQualifierNames = @($wmiClass.Qualifiers | Select-Object -ExpandProperty Name)

					#endregion

					#region Skip amendment, deprecated, action, error, indication, system, security and perfmon classes.

					<#
					PowerShell 3.0 Breaking Change:
					I had to change everywhere I was accessing a Qualifiers collection because I was getting
					a "Not found" error when I would try to see if a Qualifier was present or not. To reproduce
					this error, but a breakpoint on the next line. When you hit that breakpoint, in the nested
					debug prompt, invoke the following command:
					$wmiClass.Qualifiers['Amendment']
					If you see the "Not found" error, then you have successfully reproduced the bug.
					#>
                     
					if ((($classQualifierNames -contains 'Amendment') -and $wmiClass.Qualifiers['Amendment'].Value) -or
					    (($classQualifierNames -contains 'DEPRECATED') -and $wmiClass.Qualifiers['DEPRECATED'].Value) -or
					    (($classQualifierNames -contains 'Description') -and ($wmiClass.Qualifiers['Description'].Value -eq 'Deprecated')) -or
					    (@($classQualifierNames | Where-Object {@('Description','LOCALE') -notcontains $_}).Count -eq 0) -or
					    (@('CIM_Action','CIM_Error','CIM_Indication','__SystemClass','__SystemSecurity','CIM_StatisticalInformation') -contains $wmiClass.__DYNASTY)) {
						continue
					}

					#endregion

					#region If the class is an association, add the relationship to the relationship map.

					if (($classQualifierNames -contains 'Association') -and ($classQualifierNames -notcontains 'Abstract')) {
						$associationPair = @(
							$wmiClass.Properties `
								| Where-Object {$_.Type -eq [System.Management.CimType]::Reference} `
								| Select-Object -Property Name,@{Name='Type';Expression={$_.Qualifiers['CIMTYPE'].Value -replace '^ref:'}}
						)
						if ($associationPair.Count -ge 2) {
							$relationships = @()
							for ($index = 1; $index -lt $associationPair.Count; $index++) {
								$relationships += New-Object -TypeName System.Management.Automation.PSObject `
									| Add-Member -MemberType NoteProperty -Name Name -Value $wmiClass.Name -PassThru `
									| Add-Member -MemberType NoteProperty -Name Key1 -Value $associationPair[0].Name -PassThru `
									| Add-Member -MemberType NoteProperty -Name Type1 -Value $associationPair[0].Type -PassThru `
									| Add-Member -MemberType NoteProperty -Name Key2 -Value $associationPair[$index].Name -PassThru `
									| Add-Member -MemberType NoteProperty -Name Type2 -Value $associationPair[$index].Type -PassThru
							}
							foreach ($item in $associationPair) {
								foreach ($relationship in $relationships | Where-Object {@($_.Type1, $_.Type2) -contains $item.Type}) {
									if (-not $wmiRelationshipMap.ContainsKey($item.Type)) {
										$wmiRelationshipMap.Add($item.Type, @($relationship)) | Out-Null
									} else {
										$wmiRelationshipMap[$item.Type] += $relationship
									}
								}
							}
						}
					}

					#endregion

					#region If the class has a constructor or destructor, add them to the map.

					foreach ($method in $wmiClass.Methods) {
						$methodQualifierNames = $method.Qualifiers | Select-Object -ExpandProperty Name
						if (($methodQualifierNames -contains 'Constructor') -and ($method.Qualifiers['Constructor'].Value)) {
							$wmiConstructorMap[$wmiClass.Name] = $method
						} elseif (($methodQualifierNames -contains 'Destructor') -and ($method.Qualifiers['Destructor'].Value)) {
							$wmiDestructorMap[$wmiClass.Name] = $method
						}
					}
					if (($classQualifierNames -contains 'SupportsCreate') -and (-not $wmiConstructorMap.ContainsKey($wmiClass.Name))) {
						$wmiConstructorMap[$wmiClass.Name] = Get-Command -Name Set-WmiInstance
					}
					if (($classQualifierNames -contains 'SupportsDelete') -and (-not $wmiDestructorMap.ContainsKey($wmiClass.Name))) {
						$wmiDestructorMap[$wmiClass.Name] = Get-Command -Name Remove-WmiObject
					}

					#endregion
				}

				#endregion

				foreach ($wmiClass in $wmiClasses) {
					#region Store the list of qualifier names for the class.

					$classQualifierNames = @($wmiClass.Qualifiers | Select-Object -ExpandProperty Name)

					#endregion

					#region Skip amendment, deprecated, action, error, indication, system, security and perfmon classes.

					if ((($classQualifierNames -contains 'Amendment') -and $wmiClass.Qualifiers['Amendment'].Value) -or
					    (($classQualifierNames -contains 'DEPRECATED') -and $wmiClass.Qualifiers['DEPRECATED'].Value) -or
					    (($classQualifierNames -contains 'Description') -and ($wmiClass.Qualifiers['Description'].Value -eq 'Deprecated')) -or
					    (@($classQualifierNames | Where-Object {@('Description','LOCALE') -notcontains $_}).Count -eq 0) -or
					    (@('CIM_Action','CIM_Error','CIM_Indication','__SystemClass','__SystemSecurity','CIM_StatisticalInformation') -contains $wmiClass.__DYNASTY)) {
						continue
					}

					#endregion

					#region Determine what the proxy namespace folder paths will be.

					$proxyFunctionNamespace = $(if ($wmiClass.__NAMESPACE -match '^ROOT\\sms\\site_[^\\]+') {$wmiClass.__NAMESPACE -replace '^(ROOT\\sms\\site)_[^\\]+','$1'} else {$wmiClass.__NAMESPACE})
					$proxyFunctionFolder = Join-Path -Path $wmifxRoot -ChildPath $proxyFunctionNamespace
					$proxyFunctionHelpFileFolder = Join-Path -Path $proxyFunctionFolder -ChildPath $Host.CurrentUICulture.Name
					
					#endregion

					#region Determine the type name for the class we're processing.

					if (($classQualifierNames -contains 'abstract') -and ($wmiClass.Qualifiers['abstract'].Value)) {
						$wmiTypeName = "System.Management.ManagementObject#\$($wmiClass.Name)"
					} else {
						$wmiTypeName = "System.Management.ManagementObject#$proxyFunctionNamespace\$($wmiClass.Name)"
					}
					
					#endregion

					#region Skip any types we've already defined in this module.

					if ($wmiTypeDataExtensionsInternal -contains $wmiTypeName) {
						continue
					}
					$wmiTypeDataExtensionsInternal += $wmiTypeName

					#endregion

					#region Create the property adapter for the class if it does not exist yet.

					if (($classQualifierNames -contains 'abstract') -and ($wmiClass.Qualifiers['abstract'].Value)) {
						$wmiPropertyAdapterClassNamespace = "Wmifx.PropertyAdapter"
					} else {
						$wmiPropertyAdapterClassNamespace = "Wmifx.PropertyAdapter.$($proxyFunctionNamespace.ToLower() -replace '[\\/]','.')"
					}
					$wmiPropertyAdapterClassNamespaces = @($wmiPropertyAdapterClassNamespace -split '\.')
					$wmiPropertyAdapterClassNamespaceCount = $wmiPropertyAdapterClassNamespaces.Count
					$wmiPropertyAdapterClassName = "$wmiPropertyAdapterClassNamespace.$($wmiClass.Name)"
					$readOnlyPropertyNames = @()
					$classIndent = "`t" * $wmiPropertyAdapterClassNamespaceCount
					$memberIndent = $classIndent + "`t"
					if ((-not ($wmiPropertyAdapterClassName -as [System.Type])) -and ($wmiPropertyAdapters -notcontains $wmiPropertyAdapterClassName)) {
						Write-Progress -Activity $StringTable.ProgressOnUpdateWmifxTitle -Status ($StringTable.ProgressOnUpdateWmifxMessageCreatingPropertyAdapter -f $proxyFunctionNamespace,$wmiClass.Name)
						$cSharpClassDefinition = @"

${classIndent}public class $($wmiClass.Name)
${classIndent}{
"@
						$methodParameterMap = @{}
						foreach ($method in $wmiClass.Methods) {
							$methodName = $method.Name
							$methodQualifierNames = $method.Qualifiers | Select-Object -ExpandProperty Name
							if (($methodQualifierNames -contains 'DEPRECATED') -and $method.Qualifiers['DEPRECATED'].Value) {
								continue
							} elseif (($methodQualifierNames -contains 'Description') -and ($method.Qualifiers['Description'].Value -eq 'Deprecated')) {
								continue
							} elseif (($methodQualifierNames -contains 'Constructor') -and $method.Qualifiers['Constructor'].Value) {
								continue
							} elseif (($methodQualifierNames -contains 'Destructor') -and $method.Qualifiers['Destructor'].Value) {
								continue
							} elseif (($methodQualifierNames -notcontains 'Implemented') -or -not $method.Qualifiers['Implemented'].Value) {
								continue
							}

							$typeName = 'UInt32'
							if ($methodQualifierNames -contains 'Values') {
								$enumRowSeparator = "$([System.Environment]::NewLine)${memberIndent}`t"
								$typeName = "${methodName}ResultsEnum"
								$enumAssignments = @()
								$enumIds = @()
								$unmatchedId = $null
								$unmatchedDescription = $StringTable.EnumValueNotFound
								for ($index = 0; $index -lt $method.Qualifiers['Values'].Value.Count; $index++) {
									$enumDescription = $method.Qualifiers['Values'].Value[$index]
									$enumIdParts = @($enumDescription -split '\s|\.' -replace '[^a-z0-9]')
									for ($enumIdPartIndex = 0; $enumIdPartIndex -lt $enumIdParts.Count; $enumIdPartIndex++) {
										if ($enumIdParts[$enumIdPartIndex] -cmatch '^[a-z]') {
											$enumIdParts[$enumIdPartIndex] = $enumIdParts[$enumIdPartIndex].Substring(0,1).ToUpper() + $enumIdParts[$enumIdPartIndex].Substring(1)
										}
									}
									$revision = 1
									$enumId = -join $enumIdParts
									if ($enumId -match '^\d+') {
										$enumId = "Value$enumId"
									}
									$defaultEnumId = $enumId
									while ($enumIds -contains $enumId) {
										$enumId = "${defaultEnumId}${revision}"
										$revision++
									}
									$enumIds += $enumId
									if ($methodQualifierNames -notcontains 'ValueMap') {
										$enumAssignments += @("[Description(""$enumDescription"")]",("$enumId = unchecked((int)0x{0:x})" -f [System.UInt32]$index)) -join $enumRowSeparator
									} elseif (($method.Qualifiers['ValueMap'].Value[$index] -as [System.UInt32]) -ne $null) {
										$enumAssignments += @("[Description(""$enumDescription"")]",("$enumId = unchecked((int)0x{0:x})" -f [System.UInt32]$method.Qualifiers['ValueMap'].Value[$index])) -join $enumRowSeparator
									} elseif (-not $unmatchedId) {
										$unmatchedId = $enumId
										if ($enumId -ne $enumDescription) {
											$unmatchedDescription = $enumDescription
										}
									}
								}
								$enumPropertyMap[$method.Name] = $enumIds
								if (-not $unmatchedId) {
									if ($enumIds -notcontains 'Other') {
										$unmatchedId = 'Other'
									} else {
										$unmatchedId = 'UnmatchedEnumerationValue'
									}
								}
								$enumAssignments += @("[Description(""$unmatchedDescription"")]", "$unmatchedId = 0x0badfeed") -join $enumRowSeparator
								$enumAssignments += @("[Description(""$($StringTable.EnumValueNotSet)"")]", 'NullValue = 0x0badf00d') -join $enumRowSeparator
								$cSharpClassDefinition += @"

${memberIndent}public enum $typeName
${memberIndent}{
${memberIndent}	$($enumAssignments -join ",$enumRowSeparator")
${memberIndent}}

"@
							}

							try {
								if (($method.InParameters -eq $null) -or ($method.InParameters.PSBase.Properties.Count -eq 0)) {
									continue
								} elseif ($method.Name -notmatch '^(Set|Change|Modify|Update)') {
									continue
								}

								$inParameterCount = 0
								foreach ($inParameter in $method.InParameters.PSBase.Properties) {
									$inParameterQualifierNames = $inParameter.Qualifiers | Select-Object -ExpandProperty Name
									if ((($inParameterQualifierNames -contains 'In') -and $inParameter.Qualifiers['In'].Value) -or
									    (($inParameterQualifierNames -contains 'Optional') -and $inParameter.Qualifiers['Optional'].Value)) {
										$inParameterCount++
									}
								}
								if ($inParameterCount -eq $method.InParameters.PSBase.Properties.Count) {
									$methodParameterMap[$method.Name] = @{
										'InParameters' = @($method.InParameters.PSBase.Properties)
										'OutParameters' = @($method.OutParameters.PSBase.Properties)
										'ReturnValueType' = $typeName
									}
								}
							} catch {
							}
						}
						$enumPropertyMap = @{}
						foreach ($property in (@($wmiClass.SystemProperties) + @($wmiClass.Properties))) {
							$propertyQualifierNames = $property.Qualifiers | Select-Object -ExpandProperty Name
							if (($propertyQualifierNames -contains 'DEPRECATED') -and ($property.Qualifiers['DEPRECATED'].Value)) {
								continue
							}
							if (($propertyQualifierNames -contains 'Description') -and ($property.Qualifiers['Description'].Value -eq 'Deprecated')) {
								continue
							}
							$propertyName = $property.Name
							if ($propertyName -cmatch '^[a-z]') {
								$propertyName = $propertyName.SubString(0,1).ToUpper() + $propertyName.Substring(1)
							}
							$type = ConvertTo-Type -CimType $property.Type
							$typeName = $type.FullName
							$readOnly = $true
							if ($typeName -eq 'System.DateTime') {
								$cSharpClassDefinition += @"

${memberIndent}static public $typeName get_$propertyName(PSObject psObject)
${memberIndent}{
${memberIndent}	ManagementObject wmiObject = psObject.BaseObject as ManagementObject;
${memberIndent}	return ManagementDateTimeConverter.ToDateTime((string)wmiObject.Properties["$($property.Name)"].Value);
${memberIndent}}

"@
							} elseif (($typeName -eq 'System.UInt32') -and
							          ($propertyQualifierNames -contains 'Values')) {
								$enumRowSeparator = "$([System.Environment]::NewLine)${memberIndent}`t"
								$typeName = "${propertyName}Enum"
								$enumAssignments = @()
								$enumIds = @()
								$unmatchedId = $null
								$unmatchedDescription = $StringTable.EnumValueNotFound
								for ($index = 0; $index -lt $property.Qualifiers['Values'].Value.Count; $index++) {
									$enumDescription = $property.Qualifiers['Values'].Value[$index]
									$enumIdParts = @($enumDescription -split '\s|\.' -replace '[^a-z0-9]')
									for ($enumIdPartIndex = 0; $enumIdPartIndex -lt $enumIdParts.Count; $enumIdPartIndex++) {
										if ($enumIdParts[$enumIdPartIndex] -cmatch '^[a-z]') {
											$enumIdParts[$enumIdPartIndex] = $enumIdParts[$enumIdPartIndex].Substring(0,1).ToUpper() + $enumIdParts[$enumIdPartIndex].Substring(1)
										}
									}
									$revision = 1
									$enumId = -join $enumIdParts
									if ($enumId -match '^\d+') {
										$enumId = "Value$enumId"
									}
									$defaultEnumId = $enumId
									while ($enumIds -contains $enumId) {
										$enumId = "${defaultEnumId}${revision}"
										$revision++
									}
									$enumIds += $enumId
									if ($propertyQualifierNames -notcontains 'ValueMap') {
										$enumAssignments += @("[Description(""$enumDescription"")]",("$enumId = unchecked((int)0x{0:x})" -f [System.UInt32]$index)) -join $enumRowSeparator
									} elseif (($property.Qualifiers['ValueMap'].Value[$index] -as [System.UInt32]) -ne $null) {
										$enumAssignments += @("[Description(""$enumDescription"")]",("$enumId = unchecked((int)0x{0:x})" -f [System.UInt32]$property.Qualifiers['ValueMap'].Value[$index])) -join $enumRowSeparator
									} elseif (-not $unmatchedId) {
										$unmatchedId = $enumId
										if ($enumId -ne $enumDescription) {
											$unmatchedDescription = $enumDescription
										}
									}
								}
								$enumPropertyMap[$property.Name] = $enumIds
								if (-not $unmatchedId) {
									if ($enumIds -notcontains 'Other') {
										$unmatchedId = 'Other'
									} else {
										$unmatchedId = 'UnmatchedEnumerationValue'
									}
								}
								$enumAssignments += @("[Description(""$unmatchedDescription"")]", "$unmatchedId = 0x0badfeed") -join $enumRowSeparator
								$enumAssignments += @("[Description(""$($StringTable.EnumValueNotSet)"")]", 'NullValue = 0x0badf00d') -join $enumRowSeparator
								$cSharpClassDefinition += @"

${memberIndent}public enum $typeName
${memberIndent}{
${memberIndent}	$($enumAssignments -join ",$enumRowSeparator")
${memberIndent}}
${memberIndent}static public $typeName get_$propertyName(PSObject psObject)
${memberIndent}{
${memberIndent}	ManagementObject wmiObject = psObject.BaseObject as ManagementObject;
${memberIndent}	if (wmiObject.Properties["$($property.Name)"].Value == null)
${memberIndent}		return $typeName.NullValue;
${memberIndent}	else
${memberIndent}	{
${memberIndent}		try
${memberIndent}		{
${memberIndent}			return ($typeName)(UInt32)wmiObject.Properties["$($property.Name)"].Value;
${memberIndent}		}
${memberIndent}		catch (InvalidCastException) 
${memberIndent}		{
${memberIndent}			return $typeName.$unmatchedId;
${memberIndent}		}
${memberIndent}	}
${memberIndent}}
${memberIndent}static public UInt32 get_${propertyName}Raw(PSObject psObject)
${memberIndent}{
${memberIndent}	ManagementObject wmiObject = psObject.BaseObject as ManagementObject;
${memberIndent}	return (UInt32)wmiObject.Properties["$($property.Name)"].Value;
${memberIndent}}

"@
							} else {
								$cSharpClassDefinition += @"

${memberIndent}static public $typeName get_$propertyName(PSObject psObject)
${memberIndent}{
${memberIndent}	ManagementObject wmiObject = psObject.BaseObject as ManagementObject;
${memberIndent}	return ($typeName)wmiObject.Properties["$($property.Name)"].Value;
${memberIndent}}

"@
							}
							if (($propertyQualifierNames -contains 'write') -and $property.Qualifiers['write'].Value) {
								$readOnly = $false
								$valueBeingAssigned = 'value'
								if ($typeName -eq 'System.DateTime') {
									$valueBeingAssigned = 'ManagementDateTimeConverter.ToDmtfDateTime(value)'
								} elseif ($typeName -match 'Enum$') {
									$valueBeingAssigned = '(UInt32)value'
								}
								$cSharpClassDefinition += @"

${memberIndent}static public void set_$propertyName(PSObject psObject, $typeName value)
${memberIndent}{
${memberIndent}	ManagementObject wmiObject = psObject.BaseObject as ManagementObject;
${memberIndent}	wmiObject.Properties["$($property.Name)"].Value = $valueBeingAssigned;
${memberIndent}	wmiObject.Put();
${memberIndent}}

"@
							} elseif ($methodParameterMap.Count) {
								:methodParameterLoop foreach ($methodName in $methodParameterMap.Keys) {
									if (@($methodParameterMap[$methodName].InParameters | Select-Object -ExpandProperty Name) -contains $propertyName) {
										foreach ($methodParameter in $methodParameterMap[$methodName].InParameters) {
											if ($methodParameter.Name -eq $propertyName) {
												$readOnly = $false
												$valueBeingAssigned = 'value'
												if ($typeName -eq 'System.DateTime') {
													$valueBeingAssigned = 'ManagementDateTimeConverter.ToDmtfDateTime(value)'
												}
												$cSharpClassDefinition += @"

${memberIndent}static public void set_$propertyName(PSObject psObject, $typeName value)
${memberIndent}{
${memberIndent}	ManagementObject wmiObject = psObject.BaseObject as ManagementObject;
${memberIndent}	ManagementBaseObject inParams = wmiObject.GetMethodParameters("$methodName");
${memberIndent}	inParams["$($property.Name)"] = $valueBeingAssigned;
${memberIndent}	ManagementBaseObject outParams = wmiObject.InvokeMethod("$methodName",inParams,null);
${memberIndent}	UInt32 returnValue = (UInt32)outParams["ReturnValue"];

"@
												if ($methodParameterMap[$methodName].ReturnValueType -eq 'UInt32') {
													$cSharpClassDefinition += @"
${memberIndent}	switch (returnValue)
${memberIndent}	{
${memberIndent}		case 0:
${memberIndent}			wmiObject.Properties["$($property.Name)"].Value = $valueBeingAssigned;
${memberIndent}			break;
${memberIndent}		default:
${memberIndent}			throw new Exception(string.Format("$($StringTable.ErrorFailedToSetPropertyNoDescription)", "$propertyName", "$methodName", returnValue, "$($wmiClass.Name).$methodName"));
${memberIndent}	}
${memberIndent}}

"@
												} else {
													$cSharpClassDefinition += @"
${memberIndent}	switch (returnValue)
${memberIndent}	{
${memberIndent}		case 0:
${memberIndent}			wmiObject.Properties["$($property.Name)"].Value = $valueBeingAssigned;
${memberIndent}			break;
${memberIndent}		default:
${memberIndent}			if (Enum.IsDefined(typeof(${methodName}ResultsEnum), returnValue))
${memberIndent}			{
${memberIndent}				${methodName}ResultsEnum enumValue = (${methodName}ResultsEnum)returnValue;
${memberIndent}				FieldInfo fieldInfo = enumValue.GetType().GetField(enumValue.ToString());
${memberIndent}				DescriptionAttribute[] attributes = (DescriptionAttribute[])fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);
${memberIndent}				string description;
${memberIndent}				if (attributes.Length > 0)
${memberIndent}					description = attributes[0].Description;
${memberIndent}				else
${memberIndent}					description = enumValue.ToString();
${memberIndent}				throw new Exception(string.Format("$($StringTable.ErrorFailedToSetProperty)", "$propertyName", "$methodName", returnValue, description));
${memberIndent}			}
${memberIndent}			else
${memberIndent}				throw new Exception(string.Format("$($StringTable.ErrorFailedToSetPropertyNoDescription)", "$propertyName", "$methodName", returnValue, "$($wmiClass.Name).$methodName"));
${memberIndent}	}
${memberIndent}}

"@
												}
												break methodParameterLoop;
											}
										}
										break
									}
								}
							}
							if ($readOnly) {
								$readOnlyPropertyNames += $propertyName
							}
						}

						$cSharpClassDefinition += @"
${classIndent}}
"@
						$cSharpCode = @"
using System;
using System.ComponentModel;
using System.Management;
using System.Management.Automation;
using System.Reflection;

"@
						for ($index = 0; $index -lt $wmiPropertyAdapterClassNamespaceCount; $index++) {
							$namespaceIndent = "`t" * $index
							$cSharpCode += @"

${namespaceIndent}namespace $($wmiPropertyAdapterClassNamespaces[$index])
${namespaceIndent}{
"@
						}
						$cSharpCode += $cSharpClassDefinition
						for ($index = $wmiPropertyAdapterClassNamespaceCount - 1; $index -ge 0; $index--) {
							$namespaceIndent = "`t" * $index
							$cSharpCode += @"

${namespaceIndent}}
"@
						}

						$wmiPropertyAdapterPath = Join-Path -Path $proxyFunctionFolder -ChildPath "$wmiPropertyAdapterClassName.cs"
 						if (-not (Test-Path -LiteralPath $wmiPropertyAdapterPath)) {
							Write-Progress -Activity $StringTable.ProgressOnUpdateWmifxTitle -Status ($StringTable.ProgressOnUpdateWmifxMessageWritingPropertyAdapter -f $proxyFunctionNamespace,$wmiClass.Name)
							if (-not (Test-Path -LiteralPath $proxyFunctionFolder)) {
								New-Item -Path $proxyFunctionFolder -ItemType Directory | Out-Null
							}
							$cSharpCode | Out-File -FilePath $wmiPropertyAdapterPath -Encoding UTF8 -Force
						}
					}

					#endregion

					#region Add the WMI class type data to our namespace type data set if there are any properties on the class.

					# Note: We want to create type data for all classes, even those that we are not creating functions for.

					if ($wmiClass.Properties.Count -gt 0) {
						#region Initialize the type data xml string.

						if (-not $typeData.ContainsKey($proxyFunctionNamespace)) {
							$typeData[$proxyFunctionNamespace] = ''
						}

						#endregion

						#region Determine what the predefined member names are as well as the associated xml.

						$predefinedMembersXml = ''
						$predefinedMemberPropertyNames = @()
						if ($wmiTypeDataExtensionsExternal.Keys -contains $wmiTypeName) {
							$predefinedMembersXml = $wmiTypeDataExtensionsExternal[$wmiTypeName]
							$predefinedMemberPropertyNames = @(Select-Xml -Content "<Members>$predefinedMembersXml</Members>" -XPath '/Members/*/Name' | Select-Object -ExpandProperty Node | Select-Object -ExpandProperty '#text')
						}

						#endregion

						#region Open the type data xml.

						$typeData[$proxyFunctionNamespace] += @"
<Type>
<Name>$wmiTypeName</Name>
<Members>

"@

						#endregion

						#region Add a default display property set if there isn't one already.

						if ($predefinedMemberPropertyNames -notcontains 'PSStandardMembers') {
							$referencedPropertyNames = @()
							foreach ($property in $wmiClass.Properties) {
								$propertyQualifierNames = $property.Qualifiers | Select-Object -ExpandProperty Name
								if (($propertyQualifierNames -contains 'DEPRECATED') -and ($property.Qualifiers['DEPRECATED'].Value)) {
									continue
								}
								if (($propertyQualifierNames -contains 'Description') -and ($property.Qualifiers['Description'].Value -eq 'Deprecated')) {
									continue
								}
								$referencedPropertyNames += $property.Name
							}
							$referencedPropertyString = $referencedPropertyNames -replace '^(.*)$', '              <Name>$1</Name>' -join [System.Environment]::NewLine
							$typeData[$proxyFunctionNamespace] += @"
  <MemberSet>
    <Name>PSStandardMembers</Name>
    <Members>
      <PropertySet>
      <Name>DefaultDisplayPropertySet</Name>
        <ReferencedProperties>
$referencedPropertyString
        </ReferencedProperties>
      </PropertySet>
    </Members>
  </MemberSet>

"@
						}

						#endregion

						#region Add any predefined member information from other type data xml documents.

						<#
						PowerShell 3.0 Breaking Change:
						Update-TypeData behaviour has changed in PowerShell 3.0. PrependPath does not actually put
						your type ps1xml file in a higher precedence over the PowerShell core ps1xml files. Instead
						multiple ps1xml files are considered when looking for type extensions. Therefore for 3.0 we
						have to skip the writing of the internal WMI extension information into our ps1xml file.
						#>
						if ($predefinedMembersXml -and ($PSVersionTable.PSVersion -lt [System.Version]'3.0')) {
							$typeData[$proxyFunctionNamespace] += $predefinedMembersXml
						}

						#endregion

						#region Add read-only and read-write code properties for any properties that are not already overridden.

						foreach ($property in (@($wmiClass.SystemProperties) + @($wmiClass.Properties))) {
							if ($predefinedMemberPropertyNames -contains $property.Name) {
								continue
							}
							$propertyQualifierNames = $property.Qualifiers | Select-Object -ExpandProperty Name
							if (($propertyQualifierNames -contains 'DEPRECATED') -and ($property.Qualifiers['DEPRECATED'].Value)) {
								continue
							}
							if (($propertyQualifierNames -contains 'Description') -and ($property.Qualifiers['Description'].Value -eq 'Deprecated')) {
								continue
							}
							$propertyName = $property.Name
							if ($propertyName -cmatch '^[a-z]') {
								$propertyName = $property.Name.SubString(0,1).ToUpper() + $property.Name.Substring(1)
							}
							$type = ConvertTo-Type -CimType $property.Type
							$typeName = $type.FullName
							if ($readOnlyPropertyNames -contains $propertyName) {
								$typeData[$proxyFunctionNamespace] += @"

  <CodeProperty>
    <Name>$propertyName</Name>
    <GetCodeReference>
      <TypeName>$wmiPropertyAdapterClassName</TypeName>
      <MethodName>get_$propertyName</MethodName>
    </GetCodeReference>
  </CodeProperty>
"@
							} else {
								$typeData[$proxyFunctionNamespace] += @"

  <CodeProperty>
    <Name>$propertyName</Name>
    <GetCodeReference>
      <TypeName>$wmiPropertyAdapterClassName</TypeName>
      <MethodName>get_$propertyName</MethodName>
    </GetCodeReference>
    <SetCodeReference>
      <TypeName>$wmiPropertyAdapterClassName</TypeName>
      <MethodName>set_$propertyName</MethodName>
    </SetCodeReference>
  </CodeProperty>
"@
							}
							if (($typeName -eq 'System.UInt32') -and
							    ($propertyQualifierNames -contains 'Values')) {
								$typeData[$proxyFunctionNamespace] += @"

  <CodeProperty>
    <Name>${propertyName}Raw</Name>
    <GetCodeReference>
      <TypeName>$wmiPropertyAdapterClassName</TypeName>
      <MethodName>get_${propertyName}Raw</MethodName>
    </GetCodeReference>
  </CodeProperty>
"@
							}
						}

						#endregion

						#region Close the type data xml.

						$typeData[$proxyFunctionNamespace] += @"

</Members>
</Type>

"@
						#endregion
					}

					#endregion

					#region Skip any WMI classes that are abstract or associations or security related.

					if ((($classQualifierNames -contains 'abstract') -and $wmiClass.Qualifiers['abstract'].Value) -or
					    (($classQualifierNames -contains 'Association') -and $wmiClass.Qualifiers['Association'].Value) -or
					    ($wmiClass.Properties.Count -eq 2) -or
					    (@('__SecurityRelatedClass') -contains $wmiClass.__DYNASTY) -or
						($wmiClass.Properties | Where-Object {(@('Antecedent','Dependent','Element','Setting') -contains $_.Name) -and ($_.Type -eq 'Reference')})) {
						continue
					}

					#endregion

					#region Skip any SCCM classes that aren't really meant to be retrieved directly.

					if ($wmiClass.Name -match '^SMS_CM_RES_COLL_') {
						continue;
					}

					#endregion

					#region Set a flag indicating that the version number of the wmifx module should be updated.

					$script:updateVersion = $true

					#endregion

					#region Generate the proxy function noun.

					$wmiClassNoun = $wmiClass.Name `
						-replace '(^Win32_|^CIM_|^MSFT_)' `
						-replace 'ElementData$' `
						-replace 'SettingData$','Setting'
					switch -exact ($proxyFunctionNamespace) {
 						'ROOT\cimv2\terminalservices' {
							if ($wmiClassNoun -notmatch '^TS') {
								$wmiClassNoun = "Ts$wmiClassNoun" -replace 'TerminalService','Service'
							}
							$wmiClassNoun = $wmiClassNoun -replace '^TS','Ts'
							break
						}
						'ROOT\cimv2\nv' {
							if ($wmiClassNoun -eq 'NV') {
								$wmiClassNoun = "NVidiaRoot"
							} else {
								$wmiClassNoun = "NVidia$wmiClassNoun"
							}
							break
						}
						'ROOT\Virtualization' {
							$wmiClassNoun = $wmiClassNoun -replace '^Msvm_','HyperV'
							break
						}
						'ROOT\sms\site' {
							$wmiClassNoun = $wmiClass.Name `
								-replace '^SMS_CH_(.+)$','ClientStatus$3' `
								-replace '^SMS_CI_(.+)$','CategoryInstance$3' `
								-replace '^SMS_CI(.+)$','ConfigurationItem$3' `
								-replace '^SMS_CM' `
								-replace '^SMS_DCM','Deployment' `
								-replace '^SMS_EP','EndpointProtection' `
								-replace '^SMS_G_System_(X86_)?(PC_)?(.+)$','System$3' `
								-replace '^SMS_GH_System_(X86_)?(PC_)?(.+)$','System$3History' `
								-replace '^SMS_GEH_System_(X86_)?(PC_)?(.+)$','System$3ExtendedHistory' `
								-replace '^SMS_G_USER','User' `
								-replace '^SMS_OS_','OperatingSystem' `
								-replace '^SMS_R_(.+)$','Nda$1Resource' `
								-replace '^SMS_PDF_','PackageDefinitionFile' `
								-replace '^SMS_SCI_','SiteControlItem' `
								-replace '^SMS_SII_','SiteInstallItem' `
								-replace '^SMS_SIIB_','SiteInstallItemBase' `
								-replace '^SMS_ST_','ScheduleToken' `
								-replace 'SystemSystem','System' `
								-replace 'DeploymentDeployment','Deployment' `
								-replace '_'
							if (($wmiClassNoun -notmatch 'BIOS$') -and
							    ($wmiClassNoun -notmatch 'us$') -and
							    ($wmiClassNoun -notmatch 'ics$') -and
							    ($wmiClassNoun -notmatch 'ess$') -and
							    ($wmiClassNoun -match 's$')) {
								if ($wmiClassNoun -match 'ies$') {
									$wmiClassNoun = $wmiClassNoun -replace 'ies$','y'
								} else {
									$wmiClassNoun = $wmiClassNoun -replace 's$'
								}
							}
							$wmiClassNoun = "Sccm$wmiClassNoun"
						}
					}
					$wmiClassNoun = $wmiClassNoun -replace '_'

					#region Double-check to make sure the noun name is unique (some namespaces may have the same class name).

					if ($nounList -contains $wmiClassNoun) {
						$namespaceLeaf = Split-Path -Path $proxyFunctionNamespace -Leaf
						$namespaceLeaf = $namespaceLeaf -creplace '^[a-z]',$namespaceLeaf.Substring(0,1).ToUpper()
						$namespaceLeaf = $namespaceLeaf -creplace '^[A-Z][A-Z]+$',"$($namespaceLeaf.Substring(0,1))$($namespaceLeaf.Substring(1).ToLower())"
						$wmiClassNoun = "$namespaceLeaf$wmiClassNoun"
					}

					#endregion

					<#
					# This section is a work in progress
					# Need to decide how to make the right decisions for association classes; need to review associations and create names accordingly.
					$newProxyName = "Add-$wmiClassNoun"
					$newProxyName = "New-$wmiClassNoun"
					$setProxyName = "Set-$wmiClassNoun"
					$renameProxyName = "Rename-$wmiClassNoun"
					$startProxyName = "Start-$wmiClassNoun"
					$stopProxyName = "Stop-$wmiClassNoun"
					$suspendProxyName = "Suspend-$wmiClassNoun"
					$resumeProxyName = "Resume-$wmiClassNoun"
					$enableProxyName = "Enable-$wmiClassNoun"
					$disableProxyName = "Disable-$wmiClassNoun"
					$copyProxyName = "Copy-$wmiClassNoun"
					$testProxyName = "Test-$wmiClassNoun"
					#>

					#endregion

					#region Add the WMI class noun to the noun list if it is not already present.

					if ($nounList -notcontains $wmiClassNoun) {
						$nounList += $wmiClassNoun
					}

					#endregion

					#region Create the Get proxy function if it does not exist.

					#region Define the proxy function name.

					$getProxyName = "Get-$wmiClassNoun"

					#endregion

					#region Determine what the proxy function file and folder paths will be.

					$getProxyFunctionPath = Join-Path -Path $proxyFunctionFolder -ChildPath "$getProxyName.ps1"
					$getProxyFunctionHelpFilePath = Join-Path -Path $proxyFunctionHelpFileFolder -ChildPath $getProxyName-Help.xml

					#endregion

					if (-not (Test-Path -LiteralPath $getProxyFunctionPath) -and -not (Test-Path -LiteralPath $getProxyFunctionHelpFilePath)) {

						#region Lookup WMI documentation for this class.

						$wmiSynopsis = ''
						$wmiDescription = ''
						if (($classQualifierNames -contains 'Description') -and ($descriptionQualifier = $wmiClass.Qualifiers['Description'])) {
							$wmiDescription = $descriptionQualifier.Value
							if ($wmiDescription -notmatch '\.$') {
								$wmiDescription = "${wmiDescription}." -replace '\s+\.$','.'
							}
						}
						if ($wmiDescription) {
							$wmiSynopsis = $wmiDescription -split '(?<=\.) ' | Select-Object -First 1
						}
						if ($wmiDescription) {
							$wmiDescription = "$([System.Environment]::NewLine * 2)$($wmiDescription -replace ""`r`n|[`r`n]"",[System.Environment]::NewLine)"
						}
						if ($wmiSynopsis -and ($wmiSynopsis -notmatch '^ ')) {
							$wmiSynopsis = " $wmiSynopsis"
						}

						#endregion

						#region Initialize the custom parameters section.

						$parameters = @()

						#endregion

						#region Define the replacement Property proxy function parameter.

						$parameters += @{
							Name = 'Property'
							Position = 'Named'
							Alias = 'Select'
							Type = [System.String[]]
							ValidateNotNullOrEmpty = $true
							PassThru = $true
							Description = $StringTable.PropertyParameterDescription -f $wmiClass.Name
							ConditionalPostProcess = @(
								@{
									Condition = {$Property.Count -eq 1}
									PostProcess = $ExecutionContext.InvokeCommand.NewScriptBlock(@"
Microsoft.PowerShell.Core\ForEach-Object ``
-Begin {
	`$propertyNotFoundErrorShown = `$false
} ``
-Process {
	if (Microsoft.PowerShell.Utility\Get-Member -InputObject `$_ -Name `$Property -MemberType Properties -ErrorAction SilentlyContinue) {
		`$_ | Microsoft.PowerShell.Utility\Select-Object -ExpandProperty `$Property[0]
	} elseif (-not `$propertyNotFoundErrorShown) {
		[System.String]`$message = `$PSCmdlet.GetResourceString('Parser','PropertyNotFoundForType') -f `$Property[0],'$($wmiClass.Name)'
		[System.Management.Automation.ParameterBindingException]`$exception = Microsoft.PowerShell.Utility\New-Object -TypeName System.Management.Automation.PSArgumentException -ArgumentList `$message,'Property'
		[System.Management.Automation.ErrorRecord]`$errorRecord = Microsoft.PowerShell.Utility\New-Object -TypeName System.Management.Automation.ErrorRecord -ArgumentList `$exception,'ParameterBindingException',([System.Management.Automation.ErrorCategory]::InvalidArgument),$null
		`$PSCmdlet.ThrowTerminatingError(`$errorRecord)
	}
}
"@)
								}
								@{
									PostProcess = $ExecutionContext.InvokeCommand.NewScriptBlock(@"
Microsoft.PowerShell.Core\ForEach-Object ``
-Begin {
	`$propertyNotFoundErrorShown = `$false
} ``
-Process {
	`$wmiObject = `$_
	if ((`$propertiesFound = @(Microsoft.PowerShell.Utility\Get-Member -InputObject `$wmiObject -Name `$Property -MemberType Properties -ErrorAction SilentlyContinue)) -and 
	    (`$propertiesFound.Count -eq `$Property.Count)) {
		`$wmiObject | Microsoft.PowerShell.Utility\Select-Object -Property `$Property | Microsoft.PowerShell.Core\ForEach-Object {
			if (`$wmiObject.PSTypeNames[0] -notmatch '^Selected\.') {
				`$_.PSTypeNames[0] = "Selected.`$(`$wmiObject.PSTypeNames[0])"
			}
			`$_
		}
	} elseif (-not `$propertyNotFoundErrorShown) {
		[System.String]`$missingProperty = `$Property | Microsoft.PowerShell.Core\Where-Object {`$propertiesFound -notcontains `$wmiObject} | Microsoft.PowerShell.Utility\Select-Object -First 1
		[System.String]`$message = `$PSCmdlet.GetResourceString('Parser','PropertyNotFoundForType') -f `$missingProperty,'$($wmiClass.Name)'
		[System.Management.Automation.PSArgumentException]`$exception = Microsoft.PowerShell.Utility\New-Object -TypeName System.Management.Automation.PSArgumentException -ArgumentList `$message,'Property'
		[System.Management.Automation.ErrorRecord]`$errorRecord = Microsoft.PowerShell.Utility\New-Object -TypeName System.Management.Automation.ErrorRecord -ArgumentList `$exception,'ParameterBindingException',([System.Management.Automation.ErrorCategory]::InvalidArgument),$null
		`$PSCmdlet.ThrowTerminatingError(`$errorRecord)
	}
}
"@)
								}
							)
						}

						#endregion

						#region Define the JobName proxy function parameter.

						$parameters += @{
							Name = 'JobName'
							Type = [System.String]
							ValidateNotNullOrEmpty = $true
							Description = $StringTable.JobNameParameterDescription -f $getProxyName
							BeforePipeline = {
								$PSPassThruParameters['AsJob'] = $true
							}
							PostProcess = {
								Microsoft.PowerShell.Core\ForEach-Object {
									$_.Name = $JobName
									$_
								}
							}
						}

						#endregion

						#region Define new proxy command parameters for each property that is a primary key.

						$keyPropertyIndex = 0
						foreach ($property in $wmiClass.Properties) {
							if ($property.Name -match 'ClassName') {
								continue
							}
							if (-not ($property.Qualifiers | Where-Object {$_.Name -eq 'key'})) {
								continue
							}
							$propertyName = $property.Name
							if ($propertyName -cmatch '^[a-z]') {
								$propertyName = $propertyName.SubString(0,1).ToUpper() + $propertyName.Substring(1)
							}
							$wmiPropertyDescription = ''
							if (($property.Qualifiers | Where-Object {$_.Name -eq 'Description'}) -and
								($descriptionQualifier = $property.Qualifiers['Description'])) {
								$wmiPropertyDescription = $descriptionQualifier.Value
								if ($wmiPropertyDescription -notmatch '\.$') {
									$wmiPropertyDescription = "${wmiPropertyDescription}." -replace '\s+\.$','.'
								}
							}
							if ($wmiPropertyDescription) {
								$wmiPropertyDescription = "$([System.Environment]::NewLine * 2)$($wmiPropertyDescription -replace ""`r`n|[`r`n]"",[System.Environment]::NewLine)"
							}
							$examples = @()
							if ($enumPropertyMap.ContainsKey($propertyName)) {
								$type = [System.String]
								$randomEnum = Get-Random -InputObject $enumPropertyMap[$propertyName]
								$examples += @{
									ScriptBlock = $ExecutionContext.InvokeCommand.NewScriptBlock("$getProxyName -$propertyName $randomEnum")
									Description = $StringTable.GetParameterExampleDescription -f $wmiClass.Name,$propertyName,$randomEnum
								}
								if (-not $type.IsArray) {
									$type = $type.MakeArrayType()
								}
								$parameters += @{
									Name = $propertyName
									Type = $type
									Position = ++$keyPropertyIndex
									ValidateNotNullOrEmpty = $true
									ValidateSet = $enumPropertyMap[$propertyName]
									Description = $StringTable.KeyPropertyWildcardDescription -f $propertyName,$wmiPropertyDescription
									BeforePipeline = $ExecutionContext.InvokeCommand.NewScriptBlock(@"
					if (`$wmiFilter = ConvertTo-WmiFilter -PropertyName $propertyName -FilterValue (`$$propertyName | ForEach-Object {[int][${wmiPropertyAdapterClassName}+${propertyName}Enum]`$_})) {
						if (`$PSPassThruParameters.ContainsKey('Filter')) {
							`$PSPassThruParameters['Filter'] = "(`$(`$PSPassThruParameters['Filter'])) AND (`$wmiFilter)"
						} else {
							`$PSPassThruParameters.Add('Filter',`$wmiFilter)
						}
					}
"@)
									Example = $examples
								}
							} else {
								$type = ConvertTo-Type -CimType $property.Type
								if (@([System.SByte],[System.Int16],[System.Int32],[System.Int64],[System.Byte],[System.UInt16],[System.UInt32],[System.UInt64]) -contains $type) {
									$randomInt = Get-Random -Minimum 1 -Maximum 100
									$examples += @{
										ScriptBlock = $ExecutionContext.InvokeCommand.NewScriptBlock("$getProxyName -$propertyName $randomInt")
										Description = $StringTable.GetParameterExampleDescription -f $wmiClass.Name,$propertyName,$randomInt
									}
								} elseif (@([System.Single],[System.Double]) -contains $type) {
									$randomDouble = "{0:0.0}" -f (Get-Random -Minimum 1.0 -Maximum 25.0)
									$examples += @{
										ScriptBlock = $ExecutionContext.InvokeCommand.NewScriptBlock("$getProxyName -$propertyName $randomDouble")
										Description = $StringTable.GetParameterExampleDescription -f $wmiClass.Name,$propertyName,$randomDouble
									}
								} else {
									$randomInt = Get-Random -Minimum ([int][char]'a') -Maximum ([int][char]'z')
									$randomLowerLetter = [string][char]$randomInt
									$randomUpperLetter = [string][char]($randomInt - 32)
									$examples += @{
										ScriptBlock = $ExecutionContext.InvokeCommand.NewScriptBlock("$getProxyName -$propertyName $randomLowerLetter*")
										Description = $StringTable.GetParameterStartsWithExampleDescription -f $wmiClass.Name,$propertyName,$randomUpperLetter
									}
								}
								if (-not $type.IsArray) {
									$type = $type.MakeArrayType()
								}
								$parameters += @{
									Name = $propertyName
									Type = $type
									Position = ++$keyPropertyIndex
									ValidateNotNullOrEmpty = $true
									Description = $StringTable.KeyPropertyDescription -f $propertyName,$wmiPropertyDescription
									BeforePipeline = $ExecutionContext.InvokeCommand.NewScriptBlock(@"
					if (`$wmiFilter = ConvertTo-WmiFilter -PropertyName $propertyName -FilterValue `$$propertyName) {
						if (`$PSPassThruParameters.ContainsKey('Filter')) {
							`$PSPassThruParameters['Filter'] = "(`$(`$PSPassThruParameters['Filter'])) AND (`$wmiFilter)"
						} else {
							`$PSPassThruParameters.Add('Filter',`$wmiFilter)
						}
					}
"@)
									Example = $examples
								}
							}
						}

						#endregion

						#region Define the generic SearchProperty proxy command parameter.

						$examples = @()
						if ($wmiClass.Properties | Where-Object {$_.Name -eq 'Name'}) {
							$randomInt = Get-Random -Minimum ([int][char]'a') -Maximum ([int][char]'z')
							$randomLowerLetter = [string][char]$randomInt
							$randomUpperLetter = [string][char]($randomInt - 32)
							$examples += @{
								ScriptBlock = $ExecutionContext.InvokeCommand.NewScriptBlock("$getProxyName -SearchProperty @{Name='$randomLowerLetter*'}")
								Description = $StringTable.GetParameterStartsWithExampleDescription -f $wmiClass.Name,'Name',$randomUpperLetter
							}
						}
						if ($wmiClass.Properties | Where-Object {$_.Name -eq 'Description'}) {
							$randomDescriptiveWord = Get-Random -InputObject @('drivers','service','virtualization','storage','process','network','transfer','user','logon','server')
							$examples += @{
								ScriptBlock = $ExecutionContext.InvokeCommand.NewScriptBlock("$getProxyName -SearchProperty @{Description='*$randomDescriptiveWord*'}")
								Description = $StringTable.GetParameterContainsExampleDescription -f $wmiClass.Name,'Description',$randomDescriptiveWord
							}
						}
						if ($wmiClass.Properties | Where-Object {$_.Name -eq 'Path'}) {
							$randomDriveLetter = Get-Random -InputObject @('C','D','E','F')
							$examples += @{
								ScriptBlock = $ExecutionContext.InvokeCommand.NewScriptBlock("$getProxyName -SearchProperty @{Path='${randomDriveLetter}:\*'}")
								Description = $StringTable.GetParameterIsPathExampleDescription -f $wmiClass.Name,$randomDriveLetter
							}
						}
						$parameters += @{
							Name = 'SearchProperty'
							Alias = 'Where'
							Type = [System.Collections.Hashtable]
							ValidateNotNullOrEmpty = $true
							Description = $StringTable.SearchPropertyDescription
							BeforePipeline = {
								if ($wmiFilter = ConvertTo-WmiFilter -PropertyMap $SearchProperty) {
									if ($PSPassThruParameters.ContainsKey('Filter')) {
										$PSPassThruParameters['Filter'] = "($($PSPassThruParameters['Filter'])) AND ($wmiFilter)"
									} else {
										$PSPassThruParameters.Add('Filter',$wmiFilter)
									}
								}
							}
							Example = $examples
						}

						#endregion

						#region Define the parameters to remove and the default parameter values.

						$parametersToRemove = @('Amended','Locale','DirectRead','Namespace','Class','Authentication')
						$defaultParameterValues = @{
							     'Namespace' = $(if ($wmiClass.__NAMESPACE -match '^ROOT\\sms\\site_[^\\]+') {$wmiClass.__NAMESPACE -replace '^(ROOT\\sms\\site_)[^\\]+','$1$${SiteName}'} else {$wmiClass.__NAMESPACE})
							         'Class' = $wmiClass.Name
							'Authentication' = $defaultAuthenticationLevel
						}

						#endregion

						#region Generate the WMI "get" proxy function.

						Write-Progress -Activity $StringTable.ProgressOnUpdateWmifxTitle -Status ($StringTable.ProgressOnUpdateWmifxMessageCreatingFunction -f $proxyFunctionNamespace,$wmiClass.Name,$getProxyName)

						$proxyFunction = pspx\New-ProxyFunction `
							-CommandName Get-WmiObject `
							-ProxyName $getProxyName `
							-Synopsis ($StringTable.GetProxySynopsis -f $wmiClass.Name,$wmiSynopsis) `
							-Description ($StringTable.GetProxyDescription -f $getProxyName,$wmiClass.Name,$wmiDescription) `
							-Parameter $parameters `
							-Example @(
								@{
									ScriptBlock = $ExecutionContext.InvokeCommand.NewScriptBlock($getProxyName)
									Description = $StringTable.GetParameterlessExampleDescription -f $wmiClass.Name
								}
								@{
									ScriptBlock = $ExecutionContext.InvokeCommand.NewScriptBlock("$getProxyName -ComputerName 127.0.0.1")
									Description = $StringTable.GetSimpleRemotingExampleDescription -f $wmiClass.Name
								}
								@{
									ScriptBlock = $ExecutionContext.InvokeCommand.NewScriptBlock("$getProxyName | Format-List *")
									Description = $StringTable.GetFormatListExampleDescription -f $wmiClass.Name
								}
								@{
									ScriptBlock = $ExecutionContext.InvokeCommand.NewScriptBlock("$getProxyName -Credential FABRIKAM\Administrator -ComputerName Fabrikam")
									Description = $StringTable.GetSimpleRemotingWithCredentialsExampleDescription -f $wmiClass.Name
								}
							) `
							-RelatedLink Get-WmiObject,Invoke-WmiMethod,Remove-WmiObject,Set-WmiInstance `
							-OutputType "System.Management.ManagementObject#$proxyFunctionNamespace\$($wmiClass.Name)" `
							-RemoveParameterSet list,WQLQuery,class,path `
							-RemoveParameter $parametersToRemove `
							-DefaultParameterValue $defaultParameterValues `
							-Begin $classValidationScriptBlock

						#endregion

						#region Write the WMI proxy function to disk.

						if (-not (Test-Path -LiteralPath $proxyFunctionFolder)) {
							New-Item -Path $proxyFunctionFolder -ItemType Directory | Out-Null
						}
						@"
<#
File      = $getProxyName.ps1
Author    = Kirk "Poshoholic" Munro
Publisher = Poshoholic Studios
Copyright = © 2012 Poshoholic Studios. All rights reserved.
#>

Set-StrictMode -Version Latest

$($proxyFunction.FunctionDefinition)

Export-ModuleMember -Function $getProxyName
"@ | Out-File -FilePath $getProxyFunctionPath -Encoding UTF8 -Force

						#endregion

						#region Write the WMI help file to disk.

						if (-not (Test-Path -LiteralPath $proxyFunctionHelpFileFolder)) {
							New-Item -Path $proxyFunctionHelpFileFolder -ItemType Directory | Out-Null
						}
						$proxyFunction.MamlHelpDefinition | Out-File -FilePath $getProxyFunctionHelpFilePath -Encoding UTF8 -Force

						#endregion

					}

					#endregion

					<# Everything in this section is a work in progress...

					#region Create the New proxy function for classes that support instantiation.

					if ($wmiConstructorMap.ContainsKey($wmiClass.Name)) {
						# TODO: Proxy Set-WmiInstance, use class parameter set (drop object, path, WQLquery, query, and list), use parameters pulled from the contructor if it exists, and use New-$wmiNoun for most, but use Add- when the constructor method starts with Add and Start- when the object noun ends with process to define the object
					}

					#endregion

					#region Create the Remove proxy function for classes that support deletion.

					if ($wmiDestructorMap.ContainsKey($wmiClass.Name)) {
						$removeProxyName = "Remove-$wmiClassNoun"
						if (($wmiDestructorMap[$wmiClass.Name] -is [System.Management.MethodData]) -and
						    ($wmiClassNoun -match '(Process|Job)$')) {
							$removeProxyName = "Stop-$wmiClassNoun"
						}
						# TODO: Define parameters, make sure it works in pipeline, make sure help makes sense, refine examples.

						#region Initialize the custom parameters section.

						$parameters = @()

						#endregion

						#region Define proxy command parameters for each Get-WmiObject parameter that is missing.

						#region Define the Filter proxy function parameter.

						$parameters += @{
							Name = 'Filter'
							Type = [System.String]
							ValidateNotNullOrEmpty = $true
							ParameterSets = @{
								'class' = @{
									Description = @"
Specifies a Where clause to use as a filter. Uses the syntax of the WMI Query Language (WQL).

For more information about the Filter parameter, see the documentation in the Get-$wmiClassNoun command help.
"@
								}
							}
						}

						#endregion

						#endregion
						
						#region Define new proxy command parameters for each property that is a primary key.

						$keyPropertyIndex = 0
						# TODO: Fix the Qualifiers usage to be 3.0 compliant
						foreach ($property in $wmiClass.Properties | Where-Object {$_.Qualifiers['key'] -and ($_.Name -notmatch 'ClassName')}) {
							$propertyName = $property.Name
							if ($propertyName -cmatch '^[a-z]') {
								$propertyName = $propertyName.SubString(0,1).ToUpper() + $propertyName.Substring(1)
							}
							$wmiPropertyDescription = ''
							if ($descriptionQualifier = $property.Qualifiers['Description']) {
								$wmiPropertyDescription = $descriptionQualifier.Value
								if ($wmiPropertyDescription -notmatch '\.$') {
									$wmiPropertyDescription = "${wmiPropertyDescription}." -replace '\s+\.$','.'
								}
							}
							if ($wmiPropertyDescription) {
								$wmiPropertyDescription = "$([System.Environment]::NewLine * 2)$($wmiPropertyDescription -replace ""`r`n|[`r`n]"",[System.Environment]::NewLine)"
							}
							$beforePipelineScriptBlock = $ExecutionContext.InvokeCommand.NewScriptBlock(@"
if (`$wmiFilter = ConvertTo-WmiFilter -PropertyName $propertyName -FilterValue `$$propertyName) {
if (`$PSPassThruParameters.ContainsKey('Filter')) {
	`$PSPassThruParameters['Filter'] = "(`$(`$PSPassThruParameters['Filter'])) AND (`$wmiFilter)"
} else {
	`$PSPassThruParameters.Add('Filter',`$wmiFilter)
}
}
"@)
							$type = ConvertTo-Type -CimType $property.Type
							$examples = @()
							if (@([System.SByte],[System.Int16],[System.Int32],[System.Int64],[System.Byte],[System.UInt16],[System.UInt32],[System.UInt64]) -contains $type) {
								$randomInt = Get-Random -Minimum 1 -Maximum 100
								$examples += @{
									ScriptBlock=$ExecutionContext.InvokeCommand.NewScriptBlock("$getProxyName -$propertyName $randomInt | $removeProxyName")
									Description="This command removes any $($wmiClass.Name) objects on a computer with a $propertyName value of $randomInt."
								}
								$randomInt = Get-Random -Minimum 1 -Maximum 100
								$examples += @{
									ScriptBlock=$ExecutionContext.InvokeCommand.NewScriptBlock("$removeProxyName -$propertyName $randomInt")
									Description="This command also removes any $($wmiClass.Name) objects on a computer with a $propertyName value of $randomInt."
								}
							} elseif (@([System.Single],[System.Double]) -contains $type) {
								$randomDouble = "{0:0.0}" -f (Get-Random -Minimum 1.0 -Maximum 25.0)
								$examples += @{
									ScriptBlock=$ExecutionContext.InvokeCommand.NewScriptBlock("$getProxyName -$propertyName $randomDouble | $removeProxyName")
									Description="This command removes any $($wmiClass.Name) objects on a computer with a $propertyName value of $randomDouble"
								}
								$randomDouble = "{0:0.0}" -f (Get-Random -Minimum 1.0 -Maximum 25.0)
								$examples += @{
									ScriptBlock=$ExecutionContext.InvokeCommand.NewScriptBlock("$removeProxyName -$propertyName $randomDouble")
									Description="This command also removes any $($wmiClass.Name) objects on a computer with a $propertyName value of $randomDouble"
								}
							} else {
								$randomInt = Get-Random -Minimum ([int][char]'a') -Maximum ([int][char]'z')
								$randomLowerLetter = [string][char]$randomInt
								$randomUpperLetter = [string][char]($randomInt - 32)
								$examples += @{
									ScriptBlock=$ExecutionContext.InvokeCommand.NewScriptBlock("$getProxyName -$propertyName $randomLowerLetter* | $removeProxyName")
									Description="This command removes any $($wmiClass.Name) objects on a computer with a $propertyName that starts with '$randomUpperLetter'."
								}
								$randomInt = Get-Random -Minimum ([int][char]'a') -Maximum ([int][char]'z')
								$randomLowerLetter = [string][char]$randomInt
								$randomUpperLetter = [string][char]($randomInt - 32)
								$examples += @{
									ScriptBlock=$ExecutionContext.InvokeCommand.NewScriptBlock("$removeProxyName -$propertyName $randomLowerLetter*")
									Description="This command also removes any $($wmiClass.Name) objects on a computer with a $propertyName that starts with '$randomUpperLetter'."
								}
							}
							if (-not $type.IsArray) {
								$type = $type.MakeArrayType()
							}
							$parameters += @{
								Name = $propertyName
								Type = $type
								ValidateNotNullOrEmpty = $true
								ParameterSets = @{
									'class' = @{
										Position = ++$keyPropertyIndex
										Description = @"
Specifies $propertyName as a filter.$wmiPropertyDescription

Supports exact matches or wildcards. When multiple values are provided, any of those values will result in a match.
"@
									}
								}
								BeforePipeline = $beforePipelineScriptBlock
								Example = $examples
							}
						}

						#endregion

						#region Define the generic SearchProperty proxy command parameter.

						$examples = @()
						if ($wmiClass.Properties['Name']) {
							$randomInt = Get-Random -Minimum ([int][char]'a') -Maximum ([int][char]'z')
							$randomLowerLetter = [string][char]$randomInt
							$randomUpperLetter = [string][char]($randomInt - 32)
							$examples += @{
								ScriptBlock=$ExecutionContext.InvokeCommand.NewScriptBlock("$removeProxyName -SearchProperty @{Name='$randomLowerLetter*'}")
								Description="This command removes any $($wmiClass.Name) objects on a computer with a Name that starts with '$randomUpperLetter'."
							}
						}
						if ($wmiClass.Properties['Description']) {
							$randomDescriptiveWord = Get-Random -InputObject @('drivers','service','virtualization','storage','process','network','transfer','user','logon','server')
							$examples += @{
								ScriptBlock=$ExecutionContext.InvokeCommand.NewScriptBlock("$removeProxyName -SearchProperty @{Description='*$randomDescriptiveWord*'}")
								Description="This command removes any $($wmiClass.Name) objects on a computer with a Description that contains '$randomDescriptiveWord'."
							}
						}
						if ($wmiClass.Properties['Path']) {
							$randomDriveLetter = Get-Random -InputObject @('C','D','E','F')
							$examples += @{
								ScriptBlock=$ExecutionContext.InvokeCommand.NewScriptBlock("$removeProxyName -SearchProperty @{Path='${randomDriveLetter}:\*'}")
								Description="This command removes any $($wmiClass.Name) objects on a computer with a Path that references the ${randomDriveLetter} drive."
							}
						}
						$parameters += @{
							Name = 'SearchProperty'
							Alias = 'Where'
							Type = [System.Collections.Hashtable]
							ValidateNotNullOrEmpty = $true
							ParameterSets = @{
								'class' = @{
									Description = 'Specifies individual properties and specific values to use as a filter. Supports exact matches or wildcards. When multiple properties are provided, only objects matching all conditions will be returned. When multiple values are provided for a single property, any of those values will result in a match.'
								}
							}
							BeforePipeline = {
								if ($wmiFilter = ConvertTo-WmiFilter -PropertyMap $SearchProperty) {
									if ($PSBoundParameters.ContainsKey('Filter')) {
										$PSBoundParameters['Filter'] = "($($PSBoundParameters['Filter'])) AND ($wmiFilter)"
									} else {
										$PSBoundParameters.Add('Filter',$wmiFilter)
									}
								}
							}
							Example = $examples
						}

						#endregion

						#region Define the parameters to remove and the default parameter values.

						$parametersToRemove = @('Amended','Locale','DirectRead')
						$defaultParameterValues = @{
							'Namespace' = $wmiClass.__NAMESPACE
							    'Class' = $wmiClass.Name
						}
						if ($defaultAuthenticationLevel -ne [System.Management.AuthenticationLevel]::Default) {
							$defaultParameterValues['Authentication'] = $defaultAuthenticationLevel
						}
						$parametersToRemove += $defaultParameterValues.Keys

						#endregion

						#region Generate the WMI "remove" proxy function.

						Write-Progress -Activity $StringTable.ProgressOnUpdateWmifxTitle -Status $StringTable.ProgressOnUpdateWmifxMessageCreatingFunction -f $proxyFunctionNamespace,$wmiClass.Name,$removeProxyName)

						$proxyFunction = pspx\New-ProxyFunction `
							-CommandName Remove-WmiObject `
							-ProxyName $removeProxyName `
							-Synopsis "Remove instances of $($wmiClass.Name) Windows Management Instrumentation (WMI) class.$wmiSynopsis" `
							-Description @"
The $removeProxyName command removes instances of the $($wmiClass.Name) WMI class.$wmiDescription

The ComputerName parameter can always be used to specify a remote computer.

The $removeProxyName command does not use the Windows PowerShell remoting infrastructure to perform remote operations. You can use the ComputerName parameter of the $removeProxyName command even if your computer does not meet the requirements for Windows PowerShell remoting and even if your computer is not configured for remoting in Windows PowerShell.
"@ `
							-Parameter $parameters `
							-Example @(
								@{
									ScriptBlock=$ExecutionContext.InvokeCommand.NewScriptBlock("$removeProxyName -ComputerName 127.0.0.1")
									Description="This command removes all $($wmiClass.Name) objects on the remote computer. It identifies the computer to use by specifying the Internet Protocol (IP) address 127.0.0.1. You can change this IP address to any other valid IP address on your network so that you can display information about the services on that remote computer. By default, the account you are running under must be a member of the local administrators group on the remote computer that you specify."
								}
								@{
									ScriptBlock=$ExecutionContext.InvokeCommand.NewScriptBlock("$removeProxyName -Credential FABRIKAM\Administrator -ComputerName Fabrikam")
									Description="This command removes all $($wmiClass.Name) objects from a computer named Fabrikam. It specifies a user account name by using the Credential parameter, which causes a dialog box to be displayed in which you enter the corresponding password."
								}
							) `
							-RelatedLink $getProxyName,Remove-WmiObject,Get-WmiObject,Invoke-WmiMethod,Set-WmiInstance `
							-RemoveParameterSet path,WQLQuery,query,list `
							-RemoveParameter $parametersToRemove `
							-DefaultParameterValue $defaultParameterValues `
							-Begin $classValidationScriptBlock

						#endregion
					}

					#endregion

					#region Create the Set proxy function for classes that have writable properties.

					if ($true) {
						# TODO: Proxy Remove-WmiObject, use class and object parameter sets (drop path, WQLquery, query, and list), and use Remove-$wmiNoun for most, but use Stop- when the destructor is called stop or terminate.
					}

					#endregion

					#>

				}

				#region Write the type data files to disk.

				foreach ($namespaceKey in $typeData.Keys) {
					Write-Progress -Activity $StringTable.ProgressOnUpdateWmifxTitle -Status ($StringTable.ProgressOnUpdateWmifxMessageWritingTypesPs1xml -f $namespaceKey)
					$namespaceTypeDataPath = Join-Path -Path $wmifxRoot\$namespaceKey -ChildPath 'types.ps1xml'
					$defaultNamespaceTypeDataPath = $namespaceTypeDataPath
					$revision = 1
					while (Test-Path -LiteralPath $namespaceTypeDataPath) {
						$namespaceTypeDataPath = $defaultNamespaceTypeDataPath -replace '\.ps1xml$', "${revision}.ps1xml"
						$revision++
					}
					if (-not (Test-Path -LiteralPath $wmifxRoot\$namespaceKey)) {
						New-Item -Path $wmifxRoot\$namespaceKey -ItemType Directory | Out-Null
					}
					@"
$typeDataHeader
$($typeData.$namespaceKey -replace "`r`n|[`r`n]",[System.Environment]::NewLine)
$typeDataFooter
"@| Out-File -FilePath $namespaceTypeDataPath -Encoding UTF8 -Force
				}

				#endregion
			}

			#region Update the wmifx module manifest and create missing module files from the template if necessary.

			Write-Progress -Activity $StringTable.ProgressOnUpdateWmifxTitle -Status $StringTable.ProgressOnUpdateWmifxMessageModuleManifest

			if (Get-Variable -Name updateVersion -Scope Script -ErrorAction SilentlyContinue) {
				#region Identify the wmifx template path.

				$wmifxTemplatePath = Join-Path -Path $PSScriptRoot -ChildPath wmifxTemplate

				#endregion

				#region Write the updated manifest to the module.

				# When updating the manifest, we need to modify the version, exported function list, type file list, format file list and file list.
				Remove-Variable -Name updateVersion -Scope Script
				$manifestPath = Join-Path -Path $wmifxRoot -ChildPath wmifx.psd1
				$manifestSourcePath = $manifestPath
				if (-not (Test-Path -LiteralPath $manifestSourcePath)) {
					$manifestSourcePath = Join-Path -Path $wmifxTemplatePath -ChildPath wmifx.psd1
				}
				$rawManifestContent = @(Get-Content -LiteralPath $manifestSourcePath) -join [System.Environment]::NewLine
				$manifest = & $ExecutionContext.InvokeCommand.NewScriptBlock($rawManifestContent)
				$manifest.FileList = @()
				foreach ($file in Get-ChildItem -LiteralPath $wmifxTemplatePath -Recurse -Force) {
					if ($file.PSIsContainer) {
						continue
					}
					$relativePath = $file.FullName -replace "^$([System.Text.RegularExpressions.Regex]::Escape($wmifxTemplatePath))",'.'
					if ($file.Extension -ne '.cs') {
						$manifest.FileList += $relativePath
					}
				}
				$manifest.FunctionsToExport = @()
				foreach ($file in Get-ChildItem -LiteralPath $wmifxRoot\ROOT -Recurse -Force) {
					try {
						if ($file.PSIsContainer) {
							continue
						}
						if ($file.Extension -eq '.ps1') {
							$manifest.FunctionsToExport += $file.Name -replace '\.ps1$'
						}
						$relativePath = $file.FullName -replace "^$([System.Text.RegularExpressions.Regex]::Escape($wmifxRoot))",'.'
						if ($file.Extension -ne '.cs') {
							$manifest.FileList += $relativePath
						}
					} catch {
					}
				}
				$version = [System.Version]$manifest.ModuleVersion
				$manifest.ModuleVersion = New-Object -TypeName System.Version -ArgumentList $version.Major,$version.Minor,$version.Build,($version.Revision + 1)
				$functionsToExportString = '''*'''
				if ($manifest.FunctionsToExport) {
					$functionsToExportString = "@($([System.Environment]::NewLine)`t'$($manifest.FunctionsToExport -join ""'$([System.Environment]::NewLine)`t'"")'$([System.Environment]::NewLine))"
				}
				$fileListString = '@()'
				if ($manifest.FileList) {
					$fileListString = "@($([System.Environment]::NewLine)`t'$($manifest.FileList -join ""'$([System.Environment]::NewLine)`t'"")'$([System.Environment]::NewLine))"
				}
				@"
<#
File      = wmifx.psd1
Author    = Kirk "Poshoholic" Munro
Publisher = Poshoholic Studios
Copyright = © 2012 Poshoholic Studios. All rights reserved.
#>

@{

# Script module or binary module file associated with this manifest
ModuleToProcess = '$($manifest.ModuleToProcess)'

# Version number of this module.
ModuleVersion = '$($manifest.ModuleVersion)'

# ID used to uniquely identify this module
GUID = '$($manifest.GUID)'

# Author of this module
Author = '$($manifest.Author)'

# Company or vendor of this module
CompanyName = '$($manifest.CompanyName)'

# Copyright statement for this module
Copyright = '$($manifest.Copyright)'

# Description of the functionality provided by this module
Description = '$($manifest.Description)'

# Minimum version of the Windows PowerShell engine required by this module
PowerShellVersion = '$($manifest.PowerShellVersion)'

# Functions to export from this module
FunctionsToExport = $functionsToExportString

# List of all files packaged with this module
FileList = $fileListString

}
"@ | Out-File -FilePath $manifestPath -Encoding UTF8 -Force

				#endregion

				#region Now copy over any missing files from the wmifxTemplate folder without overwriting any existing content.

				foreach ($templateFile in Get-ChildItem -LiteralPath $wmifxTemplatePath -Recurse -Force) {
					$relativePath = $templateFile.FullName -replace "^$([System.Text.RegularExpressions.Regex]::Escape($wmifxTemplatePath))"
					$destinationPath = Join-Path -Path $wmifxRoot -ChildPath $relativePath
					if ($templateFile.PSIsContainer) {
						if (-not (Test-Path -LiteralPath $destinationPath)) {
							New-Item -ItemType Directory -Path $destinationPath -ErrorAction SilentlyContinue | Out-Null
						}
					} elseif (-not (Test-Path -LiteralPath $destinationPath)) {
						Copy-Item -LiteralPath $templateFile.FullName -Destination $destinationPath -Force
					}
				}

				#endregion
			}

			#endregion

			Write-Progress -Activity $StringTable.ProgressOnUpdateWmifxTitle -Status $StringTable.ProgressOnUpdateWmifxMessageComplete -Completed
		}
	} catch {
		throw
	}
}